using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Documents;
using com.bodurov.SilverlightControls.XmlCodeEditor.Enums;
using com.bodurov.SilverlightControls.XmlCodeEditor.Extensions;
using com.bodurov.SilverlightControls.XmlCodeEditor.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace com.bodurov.SilverlightControls.XmlCodeEditor.Tests
{
    public abstract class BaseTokenFixture
    {

        protected static NewParagraph Expect_NewLine { get { return new NewParagraph(); } }
        
        protected static void AssertTokens(List<LinkedList<Token>> actualTokens, params ExpectedToken[] expectedTokens)
        {
            //Assert.AreEqual(expectedTokens.Length, actualTokens.Count, "The number of tokens is not as expected");

            var expected_Number_Of_Lines = (expectedTokens.Length > 0 ? 1 : 0) + expectedTokens.OfType<NewParagraph>().Count();

            Assert.AreEqual(expected_Number_Of_Lines, actualTokens.Count, 
                "The expected number of lines = {0} does not match actual number of lines = {1}",
                expected_Number_Of_Lines, actualTokens.Count);

            var i = 0;
            var isFirst = true;
            var queue = new Queue<string>(5);
            foreach(var lineOfTokens in actualTokens)
            {
                

                if (!isFirst)
                {
                    Assert.IsTrue(expectedTokens.Length > i, "The number of expected tokens ({0}) is less than the number of actual tokens", expectedTokens.Length);
                    
                    var expected = expectedTokens[i++];

                    Assert.IsTrue(expected is NewParagraph, "New line was expected as token #{0} or just after '...{1}'", i, String.Join("", queue.ToArray()));
                }
                isFirst = false;
                
                foreach (var actual in lineOfTokens)
                {
                    Assert.IsTrue(expectedTokens.Length > i, "The number of expected tokens ({0}) is less than the number of actual tokens", expectedTokens.Length);

                    var expected = expectedTokens[i++];

                    queue.Enqueue(expected.Prepare(expected.Content));
                    if (queue.Count > 5) queue.Dequeue();

                    Assert.IsNotNull(actual, "Token #{0} or just after '...{1}' cannot be null. ", i, String.Join("", queue.ToArray()));

                    Assert.IsFalse(expected is NewParagraph, "New line was NOT expected as token #{0} or just after '...{1}'", i, String.Join("", queue.ToArray()));

                    var contentIsAsExpected = String.Equals(expected.Content, actual.Content);

                    Assert.IsTrue(contentIsAsExpected,
                        "The content of token #{0} or just after '...{3}' was not as expected. Expected '{1}' while it was '{2}'. ",
                        i, expected.Prepare(expected.Content), expected.Prepare(actual.Content), String.Join("", queue.ToArray()));
                    Assert.AreEqual(expected.Type, actual.Type, "The type of token #{0} or just after '...{1}' was not as expected. ", i, String.Join("", queue.ToArray()));
                    Assert.IsTrue((expected.Within & actual.Within) > 0, "The area of token #{0} or just after '...{1}' was not as expected. ", i, String.Join("", queue.ToArray()));
                }
            }
        }

        protected static void BuildAssertions(List<LinkedList<Token>> actualTokens)
        {
            XmlArea area = XmlArea.Undefined;
            var sb = new StringBuilder();
            var areaHasBeenClosed = true;
            sb.AppendLine("AssertTokens(tokens,");
            var isFirstLine = true;
            foreach (var lineOfTokens in actualTokens)
            {
                var newLinePending = !isFirstLine;
                foreach (var actual in lineOfTokens)
                {
                    var isAreaChanged = area != actual.Within;
                    area = actual.Within;
                    if(isAreaChanged)
                    {
                        if (!areaHasBeenClosed)
                        {
                            sb.AppendLine("),");
                        }
                        areaHasBeenClosed = false;
                        sb.AppendFormat("    Within(A.{0}", area);
                    }
                    sb.AppendLine(",");
                    if (newLinePending)
                    {
                        newLinePending = false;
                        sb.Append("        Expect_NewLine");
                        sb.AppendLine(",");
                    }
                    sb.AppendFormat("        Expect(\"{0}\",E.{1})", actual.Content.Replace("\"","\\\""), actual.Type);
                    
                }
                isFirstLine = false;
            }
            if (!areaHasBeenClosed)
            {
                areaHasBeenClosed = true;
                sb.Append(")");
            }
            sb.Append(");");
            //Dbg.Trace(sb.ToString());
        }


        protected static void AssertTokens(List<ListOfTokens> actualTokens, params ExpectedToken[][] expectedTokenGroups)
        {
            var list = new List<ExpectedToken>();
            foreach (ExpectedToken[] t in expectedTokenGroups) list.AddRange(t);
            AssertTokens(actualTokens, list.ToArray());
        }

        protected static void AssertTokens(ListOfTokens actualTokens, params ExpectedToken[][] expectedTokenGroups)
        {
            AssertTokens(new List<ListOfTokens>(new[] { actualTokens }), expectedTokenGroups);
        }


        protected void TraceTokens(List<ListOfTokens> tokens)
        {
            Dbg.TraceForEach(tokens, 
                (lineOfTokens, index) => 
                    (index == 0 ? "" : "\n") + string.Join("|", 
                        (from line in lineOfTokens select line.Content+"("+line.Within+":"+line.Type+")").ToArray()));
        }

        protected string Lines(params string[] lines)
        {
            var sb = new StringBuilder();
            for (var i = 0; i < lines.Length; i++ )
            {
                var line = lines[i];
                if (i > 0) sb.Append('\n');
                sb.Append(line);
            }
            return sb.ToString();
        }

        protected static ExpectedToken Expect(string content, XmlElement type)
        {
            return Expect(content, type, XmlArea.Undefined);
        }

        private static readonly Func<string, string> _prepareMsg = 
            s => String.IsNullOrEmpty(s) 
                    ? s 
                    : s.Replace("&", "&amp;")
                       .Replace("\n", "\\n")
                       .Replace("\t", "\\t")
                       .Replace("\r", "\\r")
                       .Replace("<", "&lt;")
                       .Replace(">", "&gt;");

        protected static ExpectedToken Expect(string content, XmlElement type, XmlArea area)
        {
            return new ExpectedToken(content, type, area, _prepareMsg);
        }
        protected static ExpectedToken Expect(string content, XmlElement type, XmlArea area, Func<string, string> prepareMsg)
        {
            return new ExpectedToken(content, type, area, prepareMsg);
        }

        protected static ExpectedToken[] Within(XmlArea area, params ExpectedToken[] expectTokens)
        {
            return expectTokens.Select(token => token.ChangeArea(area)).ToArray();
        }

        protected static void AssertAreEqual(object one, object two)
        {
            Assert.IsTrue(Object.Equals(one, two), "Expected '{0}' while the result was '{1}'", _prepareMsg(Convert.ToString(one) ?? "NULL"), _prepareMsg(Convert.ToString(two) ?? "NULL"));
        }
    }


    public class NewParagraph : ExpectedToken
    {
        public NewParagraph() : base("\n", XmlElement.WhiteSpace, XmlArea.Undefined)
        {
            
        }
    }

    public class ExpectedToken
    {
        public ExpectedToken(string content, XmlElement type, XmlArea area) : this(content, type, area, s => s)
        {
        }
        public ExpectedToken(string content, XmlElement type, XmlArea area, Func<string, string> prepare)
        {
            Content = content;
            Type = type;
            Within = area;
            Prepare = prepare;
        }
        public string Content { get; private set; }
        public XmlElement Type { get; private set; }
        public XmlArea Within { get; private set; }
        public Func<string, string> Prepare { get; private set; }

        public ExpectedToken ChangeArea(XmlArea area)
        {
            if (this.Within != XmlArea.Undefined) throw new ArgumentException("Area type can only be changed if it's set to Undefined");
            this.Within = area;
            return this;
        }
    }
}